package com.cl.ks.service;


import cn.hutool.core.io.IoUtil;
import cn.hutool.poi.excel.ExcelReader;
import cn.hutool.poi.excel.ExcelUtil;
import cn.hutool.poi.excel.ExcelWriter;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.cl.ks.entity.*;
import com.cl.ks.repository.KsDataFieldRepository;
import com.cl.ks.repository.KsDataTypeRepository;
import com.cl.ks.utils.DbUtil;
import com.cl.ks.utils.FileUploadSecurity;
import com.cl.ks.utils.QueryDslUtil;
import com.cl.mvc.service.BaseService;
import com.cl.mvc.util.UpdateUtil;
import com.querydsl.core.types.dsl.BooleanExpression;
import com.querydsl.core.types.dsl.Expressions;
import com.querydsl.jpa.impl.JPAQueryFactory;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;
import org.springframework.web.multipart.MultipartFile;
import scala.annotation.meta.field;

import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.InputStream;
import java.time.LocalDateTime;
import java.util.*;
import java.util.stream.Collectors;

import static com.cl.ks.constant.QTableConstant.*;

@Service
@Slf4j
public class KsDataTypeService extends BaseService<KsDataType> {
    private final KsDataTypeRepository ksDataTypeRepository;
    private final KsDataFieldRepository ksDataFieldRepository;
    private final KsDataFieldService ksDataFieldService;

    private final KsRuleServiceOld ksRuleServiceOld;

    private final JPAQueryFactory queryFactory;
    private final DbUtil dbUtil;


    protected KsDataTypeService(KsDataTypeRepository KsDataTypeRepository, KsDataFieldRepository ksDataFieldRepository, KsDataFieldService ksDataFieldService, KsRuleServiceOld ksRuleServiceOld, JPAQueryFactory queryFactory, DbUtil dbUtil) {
        super(KsDataTypeRepository);
        this.ksDataTypeRepository = KsDataTypeRepository;
        this.ksDataFieldRepository = ksDataFieldRepository;
        this.ksDataFieldService = ksDataFieldService;
        this.ksRuleServiceOld = ksRuleServiceOld;
        this.queryFactory = queryFactory;
        this.dbUtil = dbUtil;
    }

    public List<KsDataField> listFieldByKsFlowGraphId(String filterId, String tableName) {
        if (filterId == null) {
            throw new RuntimeException("filterId null");
        }
        if (filterId.contains(",")) {
            List<KsDataField> res = new ArrayList<>();
            for (String s : StringUtils.split(filterId, ",")) {
                res.addAll(listFieldByKsFlowGraphId(Integer.parseInt(s), tableName));
            }
            return res;
        } else {
            return listFieldByKsFlowGraphId(Integer.parseInt(filterId), tableName);
        }
    }
    public List<KsDataField> listFieldByKsFlowGraphId(Integer ksFlowGraphId, String tableName) {
        if (ksFlowGraphId == null) {
            throw new RuntimeException("ksFlowGraphId null");
        }
        KsFlowGraph ksFlowGraph = queryFactory.selectFrom(qKsFlowGraph).where(qKsFlowGraph.id.eq(ksFlowGraphId)).fetchFirst();
        if (ksFlowGraph == null) {
            throw new RuntimeException("ksFlowGraph不存在");
        }
        String ksDataTypeCodes = ksFlowGraph.getKsDataTypeCodes();
        log.info("###### ksDataTypeCodes:{}", ksDataTypeCodes);
        if (ksDataTypeCodes != null) {
            String[] split;
            if (ksDataTypeCodes.contains(",")) {
                split = ksDataTypeCodes.split(",");
            } else {
                split = new String[]{ksDataTypeCodes};
            }
            List<String> ksDataTypeCodeList = queryFactory.select(qKsDataType.code).from(qKsDataType).where(qKsDataType.code.in(split).and(qKsDataType.targetTable.eq(tableName))).fetch();
            log.info("###### ksDataTypeCodeList:{}", ksDataTypeCodeList);
            List<KsDataField> ksDataFieldList = queryFactory.selectFrom(qKsDataField).where(qKsDataField.dataTypeCode.in(ksDataTypeCodeList)).fetch();
            return ksDataFieldList;
        }
        return new ArrayList<>();


    }

    public KsDataType editPower(KsDataType param) {
        Optional<KsDataType> ksDataTypeOptional = findById(param.getId());
        if (ksDataTypeOptional.isPresent()) {
            KsDataType ksDataType = ksDataTypeOptional.get();
            ksDataType.setPower(param.getPower());
            return save(ksDataType);
        }
        throw new RuntimeException("数据定义不存在");
    }

    public List<KsDataField> generateDataTypeField(KsDataType ksDataType) {
        String targetTable = ksDataType.getTargetTable();
        String dbCode = ksDataType.getDbCode();
        List<KsDataField> ksDataFieldList = ksDataType.getKsDataFieldList();
        List<KsDataField> res = new ArrayList<>();
        List<String> ksDataFieldCodeList = ksDataFieldList.stream().map(KsDataField::getCode).collect(Collectors.toList());
        List<Map<String, Object>> fieldList = dbUtil.showFullColumns(dbCode, targetTable);
        fieldList.forEach(field -> {
            String fieldName = String.valueOf(field.getOrDefault("Field", ""));
            if (!ksDataFieldCodeList.contains(fieldName)) {
                String comment = String.valueOf(field.get("Comment"));
                if (!StringUtils.hasLength(comment) || "null".equals(comment)) {
                    comment = fieldName;
                }
                String fieldType = String.valueOf(field.getOrDefault("Type", ""));
                KsDataField ksDataField = new KsDataField();
                ksDataField.setCode(fieldName);
                ksDataField.setFieldName(comment);
                ksDataField.setFieldType(fieldType);
                ksDataField.setFieldMapping(fieldName);
                res.add(ksDataField);
            }

        });
        return res;
    }

    public List<KsDataField> listColumns(String targetTable, String dbCode) {
        List<KsDataField> ksDataFieldList = new ArrayList<>();
        List<Map<String, Object>> fieldList = dbUtil.showFullColumns(dbCode, targetTable);
        fieldList.forEach(field -> {
            String fieldName = String.valueOf(field.getOrDefault("Field", ""));
            String comment = String.valueOf(field.get("Comment"));
            if (!StringUtils.hasLength(comment)) {
                comment = fieldName;
            }
            String fieldType = String.valueOf(field.getOrDefault("Type", ""));
            KsDataField ksDataField = new KsDataField();
            ksDataField.setCode(fieldName);
            ksDataField.setFieldName(comment);
            ksDataField.setFieldType(fieldType);
            ksDataFieldList.add(ksDataField);
        });
        return ksDataFieldList;
    }

//    public KsDataType save(KsDataType ksDataType) {
//        KsDataType res;
//
//        if (ksDataType.getTargetTable() != null && ksDataType.getDbCode() != null) {
//            List<Map<String, Object>> fieldList = dbUtil.fetchSQL(ksDataType.getDbCode(), "show full columns from " + ksDataType.getTargetTable());
//            List<KsDataField> ksDataFieldList = ksDataType.getKsDataFieldList();
//            if (ksDataFieldList != null) {
//                fieldList.stream()
//                        .filter(field -> ksDataFieldList.stream().noneMatch(ksDataField -> String.valueOf(field.getOrDefault("Field", "")).equalsIgnoreCase(ksDataField.getCode())))
//                        .forEach(field -> {
//                            String fieldName = String.valueOf(field.getOrDefault("Field", ""));
//                            String comment = String.valueOf(field.get("Comment"));
//                            if (!StringUtils.hasLength(comment)) {
//                                comment = fieldName;
//                            }
//                            String fieldType = String.valueOf(field.getOrDefault("Type", ""));
//                            KsDataField ksDataField = new KsDataField();
//                            ksDataField.setCode(fieldName);
//                            ksDataField.setFieldName(comment);
//                            ksDataField.setFieldType(fieldType);
//                            ksDataFieldList.add(ksDataField);
//                        });
//            }
//        }
//
//        if (ksDataType.getId() != null) {
//            KsDataType target = findById(ksDataType.getId()).get();
//            UpdateUtil.copyNullProperties(ksDataType, target);
//            res = repository.save(target);
//        } else {
//            res = repository.save(ksDataType);
//        }
//        return res;
//
//    }

    public JSONArray exportKsDataTypes(List<String> ksDataTypeCodes) {

        JSONArray res = new JSONArray();
        for (String ksDataTypeCode : ksDataTypeCodes) {
            KsDataType param = new KsDataType();
            param.setCode(ksDataTypeCode);
            Optional<KsDataType> ksDataTypeOptional = findByEntity(param);
            if (!ksDataTypeOptional.isPresent()) {
                continue;
            }
            KsDataType ksDataType = ksDataTypeOptional.get();
            res.add(ksDataType);
        }
        return res;
    }

    public void importKsDataType(KsDataType ksDataType) {
        try {
            KsDataType param = new KsDataType();
            param.setCode(ksDataType.getCode());
            Optional<KsDataType> entity = findByEntity(param);
            if (!entity.isPresent()) {
                KsDataType newKsDataType = ksDataTypeRepository.save(ksDataType);
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    public void importKsDataTypes(List<KsDataType> ksDataTypeList) {
        for (KsDataType ksDataType : ksDataTypeList) {
            ksDataType.setKsDataFieldList(null);
            importKsDataType(ksDataType);
        }
    }


    public String exportKsDataType(List<String> ksDataTypeCodes, HttpServletResponse response) {
        List<Map<String, String>> rows = new ArrayList<>();

        for (String ksDataTypeCode : ksDataTypeCodes) {
            KsDataType param = new KsDataType();
            param.setCode(ksDataTypeCode);
            Optional<KsDataType> ksDataTypeOptional = findByEntity(param);
            if (!ksDataTypeOptional.isPresent()) {
                continue;
            }
            KsDataType ksDataType = ksDataTypeOptional.get();
            List<KsDataField> ksDataFieldList = ksDataType.getKsDataFieldList();

            ksDataFieldList.forEach(ksDataField -> {
                Map<String, String> item = new LinkedHashMap<>();
                item.put("数据资源名称", ksDataType.getName());
                item.put("数据资源代码", ksDataType.getCode());
                item.put("标签", ksDataType.getTags());
                item.put("字段名称", ksDataField.getFieldName());
                item.put("字段编码", ksDataField.getCode());
                item.put("字段类型", ksDataField.getFieldType());
                item.put("来源字段", ksDataField.getFromCode());
                item.put("排序权重", String.valueOf(ksDataField.getSortWeight()));
                rows.add(item);
            });
        }

        ExcelWriter writer = ExcelUtil.getWriter();
        writer.write(rows, true);

        response.setContentType("application/vnd.ms-excel;charset=utf-8");
        response.setHeader("Content-Disposition", "attachment;filename=" + String.join("-", ksDataTypeCodes) + ".xls");
        ServletOutputStream out = null;
        try {
            out = response.getOutputStream();
        } catch (IOException e) {
            throw new RuntimeException(e);
        }

        writer.flush(out, true);
        writer.close();
        IoUtil.close(out);

        return "导出成功";
    }

    public String importKsDataType(MultipartFile file, String auth) {
        try {
            FileUploadSecurity.validateFile(file);
            String showName = jwtUtil.getShowName(auth);
            Integer userId = jwtUtil.getUserId(auth);

            String fileName = file.getOriginalFilename();
            String message = null;
            // 上传文件为空
            if (StringUtils.isEmpty(fileName)) {
                message = "没有导入文件";
                return message;
            }
            //上传文件大小为1000条数据
            if (file.getSize() > 1024 * 1024 * 10) {
                message = "上传失败: 文件大小不能超过10M!";
                return message;
            }
            InputStream inputStream = file.getInputStream();
            ExcelReader excelReader = ExcelUtil.getReader(inputStream, 0);

            List<Map<String, Object>> maps = excelReader.readAll();

            List<KsDataType> ksDataTypeList = new ArrayList<>();
            List<KsDataField> ksDataFieldList = new ArrayList<>();

            List<String> ksDataTypeExists = new ArrayList<>();

            maps.forEach(map -> {
                KsDataType ksDataType = new KsDataType();
                String ksDataTypeCode = String.valueOf(map.get("数据资源代码"));
                ksDataType.setCode(ksDataTypeCode);
                Optional<KsDataType> ksDataTypeOptional = findByEntity(ksDataType);
                if (!ksDataTypeOptional.isPresent() && !ksDataTypeExists.contains(ksDataTypeCode)) {
                    ksDataType.setName(String.valueOf(map.get("数据资源名称")));
                    ksDataType.setCreatedByShowName(showName);
                    ksDataType.setCreateTime(LocalDateTime.now());
                    ksDataType.setCreatedBy(String.valueOf(userId));
                    ksDataType.setTags(String.valueOf(map.get("标签")));
                    ksDataTypeList.add(ksDataType);
                    ksDataTypeExists.add(ksDataTypeCode);
                }
                KsDataField ksDataField = new KsDataField();
                ksDataField.setDataTypeCode(ksDataTypeCode);
                ksDataField.setCode(String.valueOf(map.get("字段编码")));
                ksDataField.setRuleFieldFlag(null);
                Optional<KsDataField> ksDataFieldOptional = ksDataFieldService.findByEntity(ksDataField);
                if (!ksDataFieldOptional.isPresent()) {
                    ksDataField.setFieldName(String.valueOf(map.get("字段名称")));
                    ksDataField.setFieldType(String.valueOf(map.get("字段类型")));
                    if (map.get("来源字段") != null) {
                        String fromCode = String.valueOf(map.get("来源字段"));
                        ksDataField.setFromCode(fromCode);
                    }
                    String sortWeight = String.valueOf(map.get("排序权重"));
                    if (sortWeight.matches("\\d+")) {
                        ksDataField.setSortWeight(Integer.valueOf(sortWeight));
                    }
                    ksDataField.setCreatedBy(String.valueOf(userId));
                    ksDataField.setCreateTime(LocalDateTime.now());
                    ksDataField.setCreatedByShowName(showName);
                    ksDataFieldList.add(ksDataField);
                }
            });
            ksDataTypeRepository.saveAll(ksDataTypeList);
            ksDataFieldRepository.saveAll(ksDataFieldList);

        } catch (IOException e) {
            throw new RuntimeException(e);
        }

        return "导入成功";
    }

    @Override
    public Page<KsDataType> page(KsDataType ksDataType, String auth) {
        Page<KsDataType> all = repository.findAll(buildSpecification(ksDataType).and(buildPowerSpecification(auth)), PageRequest.of(ksDataType.getPageNumber() - 1, ksDataType.getPageSize()));
        all.getContent().forEach(item -> {
            String dataTypeCode = item.getCode();
            Long ksRuleCount = queryFactory.selectDistinct(qRlRuleField.ruleId.count()).from(qRlRuleField).where(qRlRuleField.dataTypeCode.eq(dataTypeCode)).fetchOne();
            item.setKsRuleCount(ksRuleCount);

            Long ksFlowCount = queryFactory.select(qKsFlowGraph.count()).from(qKsFlowGraph).where(Expressions.asString(",").concat(qKsFlowGraph.ksDataTypeCodes.concat(",")).contains("," + dataTypeCode + ",")).fetchOne();
            item.setKsFlowCount(ksFlowCount);

            List<String> ksRuleField = queryFactory.selectDistinct(qRlRuleField.dataFieldCode).from(qRlRuleField).where(qRlRuleField.dataTypeCode.eq(dataTypeCode)).fetch();
            item.getKsDataFieldList().forEach(ksDataField -> {
                boolean ruleFieldFlag = ksRuleField.contains(ksDataField.getCode());
                ksDataField.setRuleFieldFlag(ruleFieldFlag);
            });
        });
        return all;
    }

    public JSONObject pageKsDataType(KsDataType ksDataType, String auth) {

        if (ksDataType.getName() == null) {
            ksDataType.setName("");
        }

        String userId = String.valueOf(jwtUtil.getUserId(auth));
        String roleId = String.valueOf(jwtUtil.getRoleId(auth));
        String depId = String.valueOf(jwtUtil.getDepId(auth));


        BooleanExpression ksFlowGraphExpression = QueryDslUtil.generatePowerExpression(qKsFlowGraph, qKsFlowGraph.power, userId, roleId, depId);
        if (ksDataType.getKsFlowGraphNames() != null && !ksDataType.getKsFlowGraphNames().isEmpty()) {
            BooleanExpression ksFlowGraphNameExpression = qKsFlowGraph.isNotNull().and(qKsFlowGraph.isNull());
            for (String ksFlowGraphName : ksDataType.getKsFlowGraphNames()) {
                ksFlowGraphNameExpression = ksFlowGraphNameExpression.or(qKsFlowGraph.name.contains(ksFlowGraphName));
            }
            ksFlowGraphExpression = ksFlowGraphExpression.and(ksFlowGraphNameExpression);
        }

        List<KsFlowGraph> ksFlowGraphList = queryFactory.selectFrom(qKsFlowGraph).where(ksFlowGraphExpression).fetch();

        Page<KsDataType> ksDataTypes = page(ksDataType, auth);
        List<KsRule> allAvailableKsRuleList = ksRuleServiceOld.listAvailableRuleByDataTypeCodes(ksDataTypes.stream().map(KsDataType::getCode).collect(Collectors.toList()), ksDataType.getKsRuleNames(), auth);
        List<KsDataType> allData = ksDataTypes.getContent().stream()
                .peek(item -> {
                    item.setKsRuleList(ksRuleServiceOld.listAvailableRuleByDataTypeCode(item.getCode(), ksDataType.getKsRuleNames(), auth));

                    List<KsFlowGraph> itemKsFlowGraphList = new ArrayList<>();
                    for (KsFlowGraph ksFlowGraph : ksFlowGraphList) {
                        if (StringUtils.hasLength(ksFlowGraph.getKsDataTypeCodes()) && ("," + ksFlowGraph.getKsDataTypeCodes() + ",").contains("," + item.getCode() + ",")) {
                            itemKsFlowGraphList.add(ksFlowGraph);
                        }
                    }
                    item.setKsFlowGraphList(itemKsFlowGraphList);
                })
                .filter(item -> {
                    boolean flag1 = true;
                    boolean flag2 = true;
                    boolean flag3 = true;
                    if (ksDataType.getKsRuleNames() != null && !ksDataType.getKsRuleNames().isEmpty()) {
                        flag1 = item.getKsRuleList() != null && !item.getKsRuleList().isEmpty();
                    }
                    if (ksDataType.getKsFlowGraphNames() != null && !ksDataType.getKsFlowGraphNames().isEmpty()) {
                        flag2 = item.getKsFlowGraphList() != null && !item.getKsFlowGraphList().isEmpty();
                    }
                    if (ksDataType.getCodes() != null && !ksDataType.getCodes().isEmpty()) {
                        flag3 = ksDataType.getCodes().contains(item.getCode());
                    }
                    return flag1 && flag2 && flag3;
                }).peek(item -> {
                    List<KsDataField> ksDataFieldList = item.getKsDataFieldList();
                    List<KsRule> ksRuleList = allAvailableKsRuleList;
                    if (ksDataFieldList == null || ksDataFieldList.isEmpty() || ksRuleList == null || ksRuleList.isEmpty()) {
                        return;
                    }
                    List<Integer> ksRuleIdList = ksRuleList.stream().map(KsRule::getId).collect(Collectors.toList());
                    List<RlRuleField> ruleDataFieldCode = queryFactory.selectFrom(qRlRuleField).where(qRlRuleField.ruleId.in(ksRuleIdList)).fetch();
                    ksDataFieldList.forEach(ksDataField -> {
                        boolean ruleFieldFlag = ruleDataFieldCode.stream().anyMatch(i -> Objects.equals(i.getDataFieldCode(), ksDataField.getCode()) && Objects.equals(i.getDataTypeCode(), ksDataField.getDataTypeCode()));
                        ksDataField.setRuleFieldFlag(ruleFieldFlag);
                    });

                }).collect(Collectors.toList());

        JSONObject res = new JSONObject();
        res.put("code", "200");
        JSONObject data = new JSONObject();
        data.put("content", allData.stream().skip((ksDataType.getPageNumber() - 1) * ksDataType.getPageSize()).limit(ksDataType.getPageSize()).collect(Collectors.toList()));
        data.put("totalElements", allData.size());
        res.put("data", data);
        res.put("message", "success");
        return res;
    }
}
